package com.avengers.core.kit;

import com.avengers.core.exception.PagingHandlerStopException;
import com.avengers.core.model.AbstractEnhancerModel;
import com.avengers.core.model.DbColumn;
import com.jfinal.plugin.activerecord.Page;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author yu.wang
 * @since 2018-04-05 下午2:58
 */
public abstract class AbstractPagingHandler<M extends AbstractEnhancerModel<M>> {
    private static final Logger logger = LoggerFactory.getLogger(AbstractPagingHandler.class);
    private static final int MAX = 1000;

    private final Integer pageSize;
    private final M condition;
    private final Boolean pageIncrease;
    private boolean shutdown;
    private final boolean logUsedTime;

    protected AbstractPagingHandler(M condition) {
        this(MAX, condition, true);
    }

    protected AbstractPagingHandler(boolean logUsedTime, M condition) {
        this(logUsedTime, MAX, condition, true);
    }

    protected AbstractPagingHandler(M condition, boolean pageIncrease) {
        this(MAX, condition, pageIncrease);
    }

    protected AbstractPagingHandler(boolean logUsedTime, M condition, boolean pageIncrease) {
        this(logUsedTime, MAX, condition, pageIncrease);
    }

    protected AbstractPagingHandler(int pageSize, M condition, boolean pageIncrease) {
        this(false, pageSize, condition, pageIncrease);
    }

    protected AbstractPagingHandler(boolean logUsedTime, int pageSize, M condition, boolean pageIncrease) {
        this.pageSize = pageSize;
        this.condition = condition;
        this.pageIncrease = pageIncrease;
        this.shutdown = false;
        this.logUsedTime = logUsedTime;
    }

    public void start(DbColumn... columns) {
        if (logUsedTime) {
            TimeKit.logUsedTime(() -> beginQuery(columns), logger());
        } else {
            beginQuery(columns);
        }
    }

    private void beginQuery(DbColumn[] columns) {
        int pageNumber = 1;
        Page<M> page;
        do {
            page = paginate(pageNumber, columns);
            try {
                if (logUsedTime) {
                    final int currentSize = null == page || null == page.getList() ? 0 : page.getList().size();
                    final Page<M> finalPage = page;
                    final int pNum = pageNumber;
                    TimeKit.logUsedTime(() -> handlerEachPage(finalPage, pNum), logger(), "pageNumber_{} handler size_{}", pageNumber, currentSize);
                } else {
                    handlerEachPage(page, pageNumber);
                }
            } catch (PagingHandlerStopException e) {
                handleException(e, null);
                break;
            } catch (Exception e) {
                handleException(e, null);
            }
            if (pageIncrease) {
                pageNumber++;
            }
        } while (null != page && !page.isLastPage() && page.getTotalPage() > 0);
    }

    private void handlerEachPage(Page<M> page, int pageNumber) {
        if (null != page && !page.getList().isEmpty()) {
            logger().info("query table_{} page_{} result size : {}", condition.getFinalTableName(), pageNumber, page.getList().size());
            for (int i = 0; i < page.getList().size(); i++) {
                M m = page.getList().get(i);
                try {
                    handle(m);
                    if (shutdown) {
                        logger().info("handler-single table {} has shutdown after handler the {} record!", condition.getFinalTableName(), i);
                        break;
                    }
                } catch (PagingHandlerStopException e) {
                    handleException(e, m);
                    throw e;
                } catch (Exception e) {
                    handleException(e, m);
                }
            }
        }
    }

    private Page<M> paginate(int pageNumber, DbColumn... columns) {
        if (null != condition) {
            logger().info("paginate {} condition_{}, pageNumber_{}, pageSize_{}", condition.getFinalTableName(), condition.toConditionSql(), pageNumber, pageSize);
            return condition.paginate(pageNumber, pageSize, false, columns);
        }
        return null;
    }

    /**
     * 单条记录处理
     *
     * @param m 数据库对象
     */
    protected abstract void handle(M m) throws Exception;

    /**
     * 异常处理
     *
     * @param throwable 异常
     * @param m         数据库对象
     */
    protected abstract void handleException(Throwable throwable, M m);

    public void shutdown() {
        this.shutdown = true;
    }

    protected Logger logger() {
        return logger;
    }
}
